package org.softmed.neodatis.persistence.impl;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.neodatis.odb.ODB;
import org.neodatis.odb.ODBRuntimeException;
import org.neodatis.odb.OID;
import org.neodatis.odb.ObjectValues;
import org.neodatis.odb.Objects;
import org.neodatis.odb.Values;
import org.neodatis.odb.core.oid.OIDFactory;
import org.neodatis.odb.core.query.IQuery;
import org.neodatis.odb.core.query.criteria.Where;
import org.neodatis.odb.impl.core.query.criteria.CriteriaQuery;
import org.neodatis.odb.impl.core.query.values.ValuesCriteriaQuery;
import org.softmed.neodatis.util.FakeODB;
import org.softmed.neodatis.util.PrimaryKey;
import org.softmed.neodatis.util.PrimaryKeyManager;
import org.softmed.neodatis.util.provider.ODBProvider;
import org.softmed.neodatis.util.reflection.ClassGeneratedFieldInfo;
import org.softmed.persistence.PersistenceManager;
import org.softmed.reflection.utils.FieldInfo;
import org.softmed.reflection.utils.ReflectionUtil;

public class ODBPersistenceManager implements PersistenceManager {

	ODBProvider provider;

	ODB odb;

	ReflectionUtil util = new ReflectionUtil();

	public ODBPersistenceManager() {

	}

	public ODBPersistenceManager(ODBProvider provider) {
		this.provider = provider;
	}

	public Objects getListFromFieldObjects(Class type, String id,
			String fieldName) throws Throwable {
		PrimaryKey key = provider.getPrimaryKeyManager().getProperPrimaryKey(
				type, id);

		if (key != null) {
			Objects objects = odb.getObjects(new ValuesCriteriaQuery(type,
					Where.equal(key.getName(), key.getValue()))
					.field(fieldName), false);

			return objects;
		}

		Objects objects = odb.getObjects(new ValuesCriteriaQuery(type,
				getObjectOID(id)).field(fieldName), false);

		return objects;

	}

	public List getListFromField(Class type, String id, String fieldName)
			throws Throwable {
		Objects objects = getListFromFieldObjects(type, id, fieldName);
		List list = new ArrayList();
		while (objects.hasNext())
			list.add(objects.next());

		return list;

	}

	protected OID getObjectOID(String id) {
		return OIDFactory.buildObjectOID(new Long(id));
	}

	public Objects getListFromFieldFirstIntance(Class type, String id,
			String fieldName) throws Throwable {

		Objects objects = odb.getObjects(new ValuesCriteriaQuery(type)
				.field(fieldName), false);

		return objects;

	}

	public List getPaginatedListFromFieldRound(Class type, String id,
			String fieldName, int initialIndex, int size) throws Throwable {
		PrimaryKey key = provider.getPrimaryKeyManager().getProperPrimaryKey(
				type, id);
		Values values = null;
		if (key != null)
			values = odb.getValues(new ValuesCriteriaQuery(type, Where.equal(
					key.getName(), key.getValue())).field(fieldName).sublist(
					fieldName, "sub", initialIndex, size, false));
		else
			values = odb.getValues(new ValuesCriteriaQuery(type,
					getObjectOID(id)).field(fieldName).sublist(fieldName,
					"sub", initialIndex, size, false));

		ObjectValues ov = values.nextValues();

		return (List) ov.getByAlias("sub");

	}

	public List getPaginatedListFromFieldFirstIntanceRound(Class type,
			String fieldName, int initialIndex, int size) throws Throwable {

		Values values = odb
				.getValues(new ValuesCriteriaQuery(type).field(fieldName)
						.sublist(fieldName, "sub", initialIndex, size, false));

		ObjectValues ov = values.nextValues();

		return (List) ov.getByAlias("sub");

	}

	public Object getListItemFromField(Class type, String id, String fieldName,
			int index) throws Throwable {
		PrimaryKey key = provider.getPrimaryKeyManager().getProperPrimaryKey(
				type, id);
		Values values = null;

		if (key != null)
			values = odb.getValues(new ValuesCriteriaQuery(type, Where.equal(
					key.getName(), key.getValue())).field(fieldName).sublist(
					fieldName, "sub", index, 1, false));
		else
			values = odb.getValues(new ValuesCriteriaQuery(type,
					getObjectOID(id)).field(fieldName).sublist(fieldName,
					"sub", index, 1, false));

		ObjectValues ov = values.nextValues();

		return ((List) ov.getByAlias("sub")).get(0);

	}

	public Object getListItemFromFieldFirstIntance(Class type,
			String fieldName, int index) throws Throwable {

		Values values = odb.getValues(new ValuesCriteriaQuery(type).field(
				fieldName).sublist(fieldName, "sub", index, 1, false));

		ObjectValues ov = values.nextValues();

		return ((List) ov.getByAlias("sub")).get(0);

	}

	public List getPaginatedListFromField(Class type, String id,
			String fieldName, int initialIndex, int finalIndex)
			throws Throwable {
		PrimaryKey key = provider.getPrimaryKeyManager().getProperPrimaryKey(
				type, id);

		Values values = null;
		if (key != null)
			values = odb.getValues(new ValuesCriteriaQuery(type, Where.equal(
					key.getName(), key.getValue())).field(fieldName).sublist(
					fieldName, "sub", initialIndex, finalIndex,false));
		else
			values = odb.getValues(new ValuesCriteriaQuery(type,
					getObjectOID(id)).field(fieldName).sublist(fieldName,
					"sub", initialIndex, finalIndex,false));

		ObjectValues ov = values.nextValues();

		return (List) ov.getByAlias("sub");

	}

	public List getPaginatedListFromFieldFirstIntance(Class type,
			String fieldName, int initialIndex, int finalIndex)
			throws Throwable {

		Values values = odb.getValues(new ValuesCriteriaQuery(type).field(
				fieldName).sublist(fieldName, "sub", initialIndex, finalIndex,false));

		ObjectValues ov = values.nextValues();

		return (List) ov.getByAlias("sub");

	}

	public int getListCountFromField(Class type, String id, String fieldName)
			throws Throwable {
		PrimaryKey key = provider.getPrimaryKeyManager().getProperPrimaryKey(
				type, id);

		Values values = null;

		if (key != null)
			values = odb.getValues(new ValuesCriteriaQuery(type, Where.equal(
					key.getName(), key.getValue())).field(fieldName).size(
					fieldName, "size"));
		else
			values = odb.getValues(new ValuesCriteriaQuery(type,
					getObjectOID(id)).field(fieldName).size(fieldName, "size"));

		ObjectValues ov = values.nextValues();
		Long size = (Long) ov.getByAlias("size");
		return size.intValue();
	}

	public int getListCountFromFieldFirstIntance(Class type, String fieldName)
			throws Throwable {

		Values values = odb.getValues(new ValuesCriteriaQuery(type).field(
				fieldName).size(fieldName, "size"));

		ObjectValues ov = values.nextValues();
		Long size = (Long) ov.getByAlias("size");
		return size.intValue();
	}

	public long count(Class type) throws Exception {
		Values values = odb.getValues(new ValuesCriteriaQuery(type)
				.count("count"));
		ObjectValues ov = values.nextValues();
		BigInteger value = (BigInteger) ov.getByAlias("count");
		return value.longValue();
	}

	public Object getById(Class type, String idFieldName, Object id)
			throws Exception {
		IQuery query = new CriteriaQuery(type, Where.equal(idFieldName, id));
		Objects results = odb.getObjects(query);
		Object object = results.getFirst();
		return object;
	}

	public Object getById(Class type, String id) throws Throwable {
		PrimaryKey key = provider.getPrimaryKeyManager().getProperPrimaryKey(
				type, id);
		if (key != null)
			try {
				return getById(type, key.getName(), key.getValue());
			} catch (Throwable t) {
				return null;
			}
		else {
			Object object = getObjectById(new Long(id));
			if (object != null && object.getClass() != type)
				return null;

			return object;
		}

	}

	public Object getFirstInstance(Class type) throws Exception {

		Objects list = odb.getObjects(type);
		Object object = null;
		if (list.hasNext())
			object = list.next();
		return object;
	}

	public Object getFieldValue(Class type, String id, String fieldName)
			throws Throwable {
		Object object = null;
		PrimaryKey key = provider.getPrimaryKeyManager().getProperPrimaryKey(
				type, id);
		try {

			Values values = null;
			if (key != null)
				values = odb
						.getValues(new ValuesCriteriaQuery(type, Where.equal(
								key.getName(), key.getValue()))
								.field(fieldName));
			else
				values = odb.getValues(new ValuesCriteriaQuery(type,
						getObjectOID(id)).field(fieldName));

			ObjectValues ov = values.nextValues();

			object = ov.getByAlias(fieldName);
		} catch (ODBRuntimeException t) {
			return null;
		}
		return object;

	}

	public Object getFieldValueFromFirstInstance(Class type, String fieldName)
			throws Exception {

		Object object = null;

		try {

			Values values = odb.getValues(new ValuesCriteriaQuery(type)
					.field(fieldName));

			ObjectValues ov = values.nextValues();

			object = ov.getByAlias(fieldName);
		} catch (ODBRuntimeException t) {
			return null;
		}
		return object;

	}

	public long save(Object obj) throws Exception {
		odb.store(obj);
		long id = getInternalIDAsLong(odb.getObjectId(obj));
		return id;
	}

	public long getInternalIDAsLong(OID id) {
		// System.out.println("class id : " + id.getClassId());
		return new Long(id.getObjectId()).intValue();
	}

	public long getInternalId(Object obj) {
		return getInternalIDAsLong(odb.getObjectId(obj));
	}

	public Object getObjectID(Object obj) throws Throwable {

		FieldInfo pkinfo = provider.getPrimaryKeyManager().getPrimaryKeyInfo(
				obj.getClass());

		Object pkey = null;
		if (pkinfo != null)
			pkey = util.getFieldValue(obj, pkinfo.getName());
		else
			pkey = getInternalId(obj);

		return pkey;
	}

	public Object getObjectById(long id) {
		OID oid = OIDFactory.buildObjectOID(id);
		return odb.getObjectFromId(oid);
	}

	// public ODB getOdb() {
	// return odb;
	// }
	//
	// public void setOdb(ODB odb) {
	// this.odb = odb;
	// }

	public Collection<FieldInfo> getGeneratedFields(Class type) {
		if (!(odb instanceof FakeODB))
			return null;

		FakeODB fake = (FakeODB) odb;
		ClassGeneratedFieldInfo genInfo = fake.getInsert()
				.getClassIDFieldRegistration().getClassFieldInfo(type);
		if (genInfo == null)
			return null;
		Map<String, FieldInfo> fields = genInfo.getFields();
		if (fields == null)
			return null;

		return fields.values();
	}

	public FieldInfo getPrimaryKeyInfo(Class type) throws Throwable {
		return provider.getPrimaryKeyManager().getPrimaryKeyInfo(type);
	}

	@Override
	public void close() throws Throwable {
		if (odb != null)
			odb.close();
		odb = null;

	}

	@Override
	public void connect() throws Throwable {
		odb = provider.getODB();
	}

	@Override
	public void delete(Object obj) throws Throwable {
		odb.delete(obj);

	}

	@Override
	public void rollback() throws Throwable {
		odb.rollback();

	}

	@Override
	public void store(Object obj) throws Throwable {
		odb.store(obj);
	}

	@Override
	public void commit() throws Throwable {
		odb.commit();
	}

	@Override
	public List getObjects(Class type) {
		List list = new ArrayList();
		Objects objs = odb.getObjects(type);
		while (objs.hasNext())
			list.add(objs.next());
		return list;
	}

	@Override
	public List getObjects(Class type, boolean inMemory, int initialIndex,
			int finalIndex) throws Throwable {

		List list = new ArrayList();
		Objects objs = odb.getObjects(type, inMemory, initialIndex, finalIndex);
		while (objs.hasNext())
			list.add(objs.next());
		return list;

	}

	public PrimaryKeyManager getPkManager() {
		return provider.getPrimaryKeyManager();
	}

}
